mod commands;
mod js_config;
mod option;
mod plugin;

use crate::commands::{Cli, Command, EmitPluginCommandOpts};
use anyhow::Result;
use clap::Parser;

use commands::CodegenOptionGroup;
use javy_codegen::{Generator, JS, LinkingKind, Plugin, SourceEmbedding, WitOptions};
use js_config::JsConfig;
use plugin::{CliPlugin, PLUGIN_MODULE, PluginKind, UninitializedPlugin};
use std::fs;
use std::fs::File;
use std::io::Write;

#[tokio::main]
async fn main() -> Result<()> {
    let args = Cli::parse();

    match &args.command {
        Command::EmitPlugin(opts) => emit_plugin(opts),
        Command::Build(opts) => {
            let js = JS::from_file(&opts.input)?;
            let codegen_opts: CodegenOptionGroup = opts.codegen.clone().try_into()?;

            // Always assume the default plugin if no plugin is provided.
            let cli_plugin = match &codegen_opts.plugin {
                Some(path) => CliPlugin::new(Plugin::new_from_path(path)?, PluginKind::User),
                None => CliPlugin::new(Plugin::new(PLUGIN_MODULE.into())?, PluginKind::Default),
            };

            let js_opts = JsConfig::from_group_values(&cli_plugin, opts.js.clone())?;

            let mut generator = Generator::new(cli_plugin.into_plugin());

            // Configure the generator with the provided options.
            generator
                .wit_opts(codegen_opts.wit)
                .js_runtime_config(js_opts.to_json()?);

            let source_embedding = match codegen_opts.source {
                commands::Source::Omitted => SourceEmbedding::Omitted,
                commands::Source::Compressed => SourceEmbedding::Compressed,
                commands::Source::Uncompressed => SourceEmbedding::Uncompressed,
            };
            generator.source_embedding(source_embedding);

            set_producer_version(&mut generator);

            if codegen_opts.dynamic {
                generator.linking(LinkingKind::Dynamic);
            } else {
                generator.linking(LinkingKind::Static);
            };

            let wasm = generator.generate(&js).await?;

            fs::write(&opts.output, wasm)?;
            Ok(())
        }
        Command::InitPlugin(opts) => {
            let plugin_bytes = fs::read(&opts.plugin)?;

            let uninitialized_plugin = UninitializedPlugin::new(&plugin_bytes)?;
            let initialized_plugin_bytes = uninitialized_plugin.initialize().await?;

            let mut out: Box<dyn Write> = match opts.out.as_ref() {
                Some(path) => Box::new(File::create(path)?),
                None => Box::new(std::io::stdout()),
            };
            out.write_all(&initialized_plugin_bytes)?;
            Ok(())
        }
    }
}

fn emit_plugin(opts: &EmitPluginCommandOpts) -> Result<()> {
    let mut file: Box<dyn Write> = match opts.out.as_ref() {
        Some(path) => Box::new(File::create(path)?),
        _ => Box::new(std::io::stdout()),
    };
    file.write_all(PLUGIN_MODULE)?;
    Ok(())
}

fn set_producer_version(generator: &mut Generator) {
    generator.producer_version(env!("CARGO_PKG_VERSION").to_string());
}
